#include"Index.h"

void Initialize(BWT* bwt, unsigned int bwtLength)
{
	bwt->textLength = bwtLength;
	bwt->occSizeInWord = BWTOccValueMinorSizeInWord(bwt->textLength);
	bwt->occMajorSizeInWord = BWTOccValueMajorSizeInWord(bwt->textLength);
	bwt->occValue = new unsigned int[bwt->occSizeInWord];
	bwt->occMajorValue = new unsigned int[bwt->occMajorSizeInWord];	
}
_
void LoadLookupTable(LookupTable *lookupTable, const char * fileName, const int mtableSize)  {

	lookupTable->tableSize = mtableSize;
	unsigned long long NR_TOP = 1 << (lookupTable->tableSize * 2);
	lookupTable->table = (unsigned int *)malloc(sizeof(unsigned) * NR_TOP);

	FILE * fin = fopen(fileName, "rb");
	unsigned step = 1048576;
	unsigned int i;
	for (i = 0; i < NR_TOP; i += step) {
		fread(lookupTable->table + i, sizeof(*(lookupTable->table)), step, fin);
	}
	fclose(fin);
}
__device__
unsigned int getOccInInt(unsigned int bwtCode, char baseId, char *occCountMap)
{
	unsigned int value = 0;
	value += occCountMap[baseId * DEVICE_COUNT_MAP_SIZE + (bwtCode&0xFF)];
	value += occCountMap[baseId * DEVICE_COUNT_MAP_SIZE + ((bwtCode >> 8)&0xFF)];
	value += occCountMap[baseId * DEVICE_COUNT_MAP_SIZE + ((bwtCode >> 16)&0xFF)];
	value += occCountMap[baseId * DEVICE_COUNT_MAP_SIZE + ((bwtCode >> 24)&0xFF)];
	return value;
}
__device__
unsigned int getCountValue(BWT* bwt,char baseId)
{
	if (baseId >= ALPHABET_SIZE || baseId < 0)
	{
		printf("invalid baseId in getCountValue!\n");
		printf("baseId = %d \n",baseId);
		return 0;
	}
	return bwt->countValue[baseId];
}

__device__
unsigned int getOccValue(BWT *bwt,unsigned int pos,char baseId)
{
	unsigned int i=0,occMajorIndex, occMinorIndex,bwtCodeIndex, currentPos,currentBWTPos, temp=0, value=0;
	unsigned int mask,shift;

	pos -= (pos > bwt->inverseSa0);

	occMajorIndex = pos / OCC_INTERVAL_MAJOR;
	value = bwt->occMajorValue[occMajorIndex* ALPHABET_SIZE + baseId];

	occMinorIndex = pos / OCC_INTERVAL;
	currentPos = occMajorIndex * OCC_INTERVAL_MAJOR;
	if(currentPos != occMinorIndex * OCC_INTERVAL)
	{
		if(occMinorIndex % OCC_VALUE_PER_WORD > 0) {
			occMinorIndex =(occMinorIndex - 1) / OCC_VALUE_PER_WORD * ALPHABET_SIZE;
			value += LowHalf(bwt->occValue[occMinorIndex + baseId]);
		}
		else {
			occMinorIndex = occMinorIndex / OCC_VALUE_PER_WORD * ALPHABET_SIZE;
			value += HighHalf(bwt->occValue[occMinorIndex + baseId]);
		}
	}
	currentPos = pos / OCC_INTERVAL * OCC_INTERVAL;
	currentBWTPos = currentPos / CHAR_PER_WORD ;

	if( currentPos != pos ) {
		if( pos % CHAR_PER_WORD > 0)
			shift = (CHAR_PER_WORD - (pos % CHAR_PER_WORD))* BIT_PER_CHAR;
		else
			shift = 0;
		mask = 0xFFFFFFFF << shift;
		bwtCodeIndex = (pos + CHAR_PER_WORD -1 )/ CHAR_PER_WORD;

		//           bwt->bwtCode[bwtCodeIndex-1] = bwt->bwtCode[bwtCodeIndex-1] & mask;
		for(i=currentBWTPos; i<bwtCodeIndex-1; i++){
			unsigned int tempBwtCode = bwt->bwtCode[i];
			value += getOccInInt(tempBwtCode, baseId, bwt->occCountMap);

		}
		if((i + 1)== bwtCodeIndex) {
			temp = (bwt->bwtCode[bwtCodeIndex-1] & mask);
			value += getOccInInt(temp, baseId, bwt->occCountMap);
			//      value += occCountMap[baseId * COUNT_MAP_SIZE + G_HighHalf(temp)];
			//      value += occCountMap[baseId * COUNT_MAP_SIZE + G_LowHalf(temp)];
		}
		if(baseId == 0)
			value -= shift/BIT_PER_CHAR;
	}
	return value;
}


__device__
void getAllOccValue(BWT *bwt, unsigned int pos,unsigned int*result)
{
	unsigned int j, occMajorIndex, occMinorIndex, bwtCodeIndex, currentPos, currentBWTCodePos;
	unsigned int mask, shift, temp;
	int i;
	pos -= pos > bwt->inverseSa0;
	//	bwt->getAllOccCount += 1;

	occMajorIndex = pos / OCC_INTERVAL_MAJOR * ALPHABET_SIZE;
	for(i=0; i<ALPHABET_SIZE; i++)
		result[i] = bwt->occMajorValue[occMajorIndex + i];

	currentPos = occMajorIndex / ALPHABET_SIZE * OCC_INTERVAL_MAJOR;
	occMinorIndex = pos / OCC_INTERVAL;
	if( currentPos != occMinorIndex * OCC_INTERVAL ) {
		if(occMinorIndex % OCC_VALUE_PER_WORD > 0) {
			occMinorIndex = (occMinorIndex-1) * ALPHABET_SIZE / OCC_VALUE_PER_WORD;
			for(i=0; i<ALPHABET_SIZE; i++) {
				result[i] += LowHalf(bwt->occValue[occMinorIndex + i]);
			}
		}
		else {
			occMinorIndex = occMinorIndex * ALPHABET_SIZE / OCC_VALUE_PER_WORD;
			for(i=0; i<ALPHABET_SIZE; i++) {
				result[i] += HighHalf(bwt->occValue[occMinorIndex + i]);
			}
		}
	}
	currentPos = pos / OCC_INTERVAL * OCC_INTERVAL;
	currentBWTCodePos = currentPos / CHAR_PER_WORD;

	if(currentPos != pos){

		bwtCodeIndex =( pos + CHAR_PER_WORD -1 )/ CHAR_PER_WORD;
		if(pos % CHAR_PER_WORD)
			shift = (CHAR_PER_WORD - (pos % CHAR_PER_WORD)) * BIT_PER_CHAR;
		else
			shift = 0;
		mask = (0xFFFFFFFF << shift);

		for(i=currentBWTCodePos; i<bwtCodeIndex-1; i++){
			for(j=0; j<ALPHABET_SIZE; j++){
				unsigned int * tempBwtCode = bwt->bwtCode[i];
				result[j] += getOccInInt(tempBwtCode, j, bwt->occCountMap);
			}
		}
		if((i+1) == bwtCodeIndex) {
			temp = (bwt->bwtCode[bwtCodeIndex-1] & mask);
			for(j=0; j<ALPHABET_SIZE; j++){
				result[j] += getOccInInt(temp, j, bwt->occCountMap);
			}
			result[0] -= shift/BIT_PER_CHAR;
		}
	}
}

__device__
bool bwtCalc(BWT *bwt, char baseId, unsigned int * start, unsigned int * end)
{
	if ((*start) > bwt->textLength || (*end) > bwt->textLength)
		return false;

//	*start = bwt->countValue[baseId] +  getOccValue(bwt, *start,baseId) + 1;
//	*end = bwt->countValue[baseId] + getOccValue(bwt, (*end+1), baseId);
	*start = getOccValue(bwt, *start,baseId) + 1;
	*end = getOccValue(bwt, (*end+1), baseId);

	if (*start <= *end)
		return true;
	return false;
}

__device__
bool bwtCalcWithOccValue(unsigned int *countValue,char baseId,unsigned int startOccValue,unsigned int endOccValue, unsigned int * start, unsigned int * end)
{
//	(*start) = countValue[baseId] + startOccValue + 1;
//	(*end) = countValue[baseId] + endOccValue;
	(*start) = startOccValue + 1;
	(*end) = endOccValue;

	if ((*start) <= (*end))
		return true;
	return false;
}


__device__
void getSaValue(BWT *bwt, unsigned int left, unsigned int right, unsigned int * saInterval)
{
	unsigned int i;
	for (i=left; i<=right; i++)
	{
		//	bwt->saInterval[i-left] = bwt->saValue[i];
	}
}


void DecodeBWT(BWT *bwt, unsigned int * tempBwtCode)
{
	//	int i,j,k;
	bwt->bwtCode = tempBwtCode;
}

unsigned int BWTResidentSizeInWord(const unsigned int numChar) {

	unsigned int numCharRoundUpToOccInterval;

	// The $ in BWT at the position of inverseSa0 is not encoded
	numCharRoundUpToOccInterval = (numChar + OCC_INTERVAL - 1) / OCC_INTERVAL * OCC_INTERVAL;

	return (numCharRoundUpToOccInterval + CHAR_PER_WORD - 1) / CHAR_PER_WORD;

}

unsigned int BWTFileSizeInWord(const unsigned int numChar) {

	// The $ in BWT at the position of inverseSa0 is not encoded
	return (numChar + CHAR_PER_WORD - 1) / CHAR_PER_WORD;

}

unsigned int BWTOccValueMinorSizeInWord(const unsigned int numChar) {

	unsigned int numOfOccValue;

	numOfOccValue = (numChar + OCC_INTERVAL - 1) / OCC_INTERVAL + 1;							// Value at both end for bi-directional encoding
	return (numOfOccValue + OCC_VALUE_PER_WORD - 1) / OCC_VALUE_PER_WORD * ALPHABET_SIZE;

}

unsigned int BWTOccValueMajorSizeInWord(const unsigned int numChar) {

	unsigned int numOfOccValue;
	unsigned int numOfOccIntervalPerMajor;

	numOfOccValue = (numChar + OCC_INTERVAL - 1) / OCC_INTERVAL + 1;                                // Value at both end for bi-directional encoding
	numOfOccIntervalPerMajor = OCC_INTERVAL_MAJOR / OCC_INTERVAL;

	return (numOfOccValue + numOfOccIntervalPerMajor - 1) / numOfOccIntervalPerMajor * ALPHABET_SIZE;

}


void BWTClearTrailingBwtCode(BWT *bwt) {

	unsigned int bwtResidentSizeInWord;
	unsigned int wordIndex, offset;
	unsigned int i;
	unsigned int textLength = bwt->textLength;

	bwtResidentSizeInWord = BWTResidentSizeInWord(textLength);

	wordIndex = textLength / CHAR_PER_WORD;
	offset = (textLength - wordIndex * CHAR_PER_WORD) * BIT_PER_CHAR;
	if (offset > 0) {
		//		printf("offset:  %d\n", offset);
		bwt->bwtCode[wordIndex] = truncateRight(bwt->bwtCode[wordIndex], BITS_IN_WORD - offset);
	} else 	if (wordIndex < bwtResidentSizeInWord) {
		bwt->bwtCode[wordIndex] = 0;
	}
	

	for (i=wordIndex+1; i<bwtResidentSizeInWord; i++) {
		bwt->bwtCode[i] = 0;
		//		printf( "bwtCode[%d] = 0\n", i);
	}
}
